/**
 * Esta clase representa a una venta realizada en
 * una sucursal.
 */
package ventas;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.joda.time.DateTime;

import productosYPresentaciones.Producto;
import sucursal.Stock;
import sucursal.Sucursal;
import clientes.Cliente;
import clientes.ListaDeCompras;
import clientes.Ticket;
import exceptions.StatesException;
import exceptions.VentaYaAbonadaException;
import exceptions.VentasException;

/**
 * @author Lucas Alan Silvestri.
 *
 */

public abstract class Venta {
	
	private Map<Producto, Integer> productosQueDeseaComprar;
	private Map<Producto, Integer> productosComprados;
	private Sucursal sucursalDondeSeRealizaLaVenta;
	private Cliente clienteQueDeseaRealizarLaCompra;
	private DateTime fechaDeCreacionDeLaVenta;
	private Ticket ticketDeVenta;
	private ListaDeCompras listaDeCompraDelCliente;
	
	/**
	 * Constructor de la clase.
	 * @param unaListaDeCompras es una lista con los productos
	 * que desea comprar el cliente.
	 * @param sucursalDondeSeRealizaLaVenta es la sucursal donde
	 * se realiza la venta.
	 * @param clienteQueDeseaRealizarLaCompra es el cliente
	 * quien quiere realizar la compra de productos.
	 */
	public Venta(ListaDeCompras unaListaDeCompras,
			Sucursal sucursalDondeSeRealizaLaVenta,
			Cliente clienteQueDeseaRealizarLaCompra){
		
		this.setProductosQueDeseaComprar(
				unaListaDeCompras.getProductosAComprar());
		this.setProductosComprados(
				new HashMap<Producto, Integer>());
		this.setSucursalDondeSeRealizaLaVenta(
				sucursalDondeSeRealizaLaVenta);
		this.setClienteQueDeseaRealizarLaCompra(
				clienteQueDeseaRealizarLaCompra);
		this.setFechaDeCreacionDeLaVenta(DateTime.now());
		this.setListaDeCompraDelCliente(unaListaDeCompras);
	}
	
	/**
	 * Realiza el proceso de compra. Caso contrario,
	 * lanzara una excepcion indicando que no pude realizarse
	 * la compra.
	 * @throws VentasException 
	 * @throws StatesException 
	 */
	public void realizarCompra() throws VentasException{
		if(seCumplenLosRequisitosParaRealizarLaCompra()){
			this.generarTicketDeCompra();
			this.cobrarProductos();
			this.getProductosComprados().putAll(
					this.getProductosQueDeseaComprar());
			this.tacharProductosComprados();
			this.removerStockDeLaSucursal();
		}
		else{
			throw new VentasException("No se pudo Realizar la compra.");
		}
	}
	
	/**
	 * @return el monto a pagar por los productos.
	 */
	protected float montoAPagarPorLosProductos(){
		float montoAPagar;
		if(this.hayOfertasQueSePuedanAplicar()){
			montoAPagar = montoAPagarConDescuentosAplicados();
		}
		else{
			Set<Map.Entry<Producto, Integer>> productos =
					this.getProductosQueDeseaComprar().entrySet();
			montoAPagar = 0F;
			for (Map.Entry<Producto, Integer> productoActual : productos) {
				montoAPagar = montoAPagar + (productoActual.getKey().precioActual()
						* productoActual.getValue());
		    }
		}
		return montoAPagar;
	}
	
	/**
	 * @return el monto que el cliente 
	 * debera pagar con los descuentos aplicados.
	 */
	protected float montoAPagarConDescuentosAplicados(){
		return this.getSucursalDondeSeRealizaLaVenta().aplicarOfertasConDescuento(
				this.getProductosQueDeseaComprar());
	}

	/**
	* verifica  ofertas existentes sobre los productos.
	* @param productosAComprar productos que desea comprar el cliente.
	* @return si hay  ofertas aplicables a los productos.
	*/
	private boolean hayOfertasQueSePuedanAplicar() {
		return this.getSucursalDondeSeRealizaLaVenta().puedeAplicarOferta(
				this.getProductosQueDeseaComprar());
		}
	
	/**
	 * @param productosATransformar productos que se adaptaran a stock.
	 * @return un stock con las los productos que desea incluir.
	 */
	protected Stock transformarAStock(Map<Producto, Integer> productosATransformar){
		Stock stock = new Stock();
		Set<Map.Entry<Producto, Integer>> productosAProcesar = 
				this.getProductosComprados().entrySet();
		 for (Map.Entry<Producto, Integer> productoActual : productosAProcesar) {
			 stock.agregarStock(productoActual.getKey(), productoActual.getValue());
		 }
		 return stock;
	}
	
	/**
	 * remueve del stock los productos ya comprados por el
	 * cliente.
	 */
	private void removerStockDeLaSucursal() {
		this.getSucursalDondeSeRealizaLaVenta().removerStockDelStockDeSucursal(
				transformarAStock(this.getProductosComprados()));
	}

	/**
	 * elimina los productos ya comprados por el
	 * cliente.
	 */
	protected void tacharProductosComprados() {
		this.getProductosQueDeseaComprar().clear();
	}
	
	/**
	 * cobrara los productos al cliente.
	 * @throws VentasException 
	 */
	protected abstract void cobrarProductos() throws VentasException;

	/**
	 * @return si hay stock y si el cliente puede pagar
	 * por los productos que desea comprar.
	 * @throws VentasException 
	 */
	protected boolean seCumplenLosRequisitosParaRealizarLaCompra() throws VentaYaAbonadaException {
		boolean seCumple;
		try { 
			seCumple = this.hayStock() & this.elClientePuedePagarLosProductos();
			} 
		catch (VentaYaAbonadaException e){ 
			seCumple = this.hayStock();
			} 
		return seCumple;
		}

	/**
	 * @return si hay stock de productos en la sucursal.
	 */
	protected boolean hayStock() {
		return this.getSucursalDondeSeRealizaLaVenta().hayStock(
				this.getProductosQueDeseaComprar());
	}

	/**
	 * @return si el cliente puede pagar los productos.
	 * @throws VentaYaAbonadaException 
	 */
	protected abstract boolean elClientePuedePagarLosProductos() throws VentaYaAbonadaException;

	/**
	 * @param unosProductos son los productos que seran procesados
	 * para ver cuantos productos hay.
	 * @return la cantidad de productos que hay.
	 */
	private int numeroDeProductos(Map<Producto, Integer> unosProductos) {
		Set<Map.Entry<Producto, Integer>> productosAProcesar = 
				unosProductos.entrySet();
		int numeroDeProductos = 0;
		for (Map.Entry<Producto, Integer> productoActual : productosAProcesar) {
			numeroDeProductos = numeroDeProductos + 
					productoActual.getValue();
		}
		return numeroDeProductos;
	}
	
	/**
	 * @return el numero de los productos que desea
	 * comprar el cliente.
	 */
	public  int numeroDeProductosAComprar() {
		return this.numeroDeProductos(this.getProductosQueDeseaComprar());
	}

	/**
	 * @return el numero de los productos que acaba de
	 * comprar el cliente.
	 */
	public int numeroDeProductosComprados() {
		return this.numeroDeProductos(this.getProductosComprados());
	}
	
	/**
	 * Entrega el ticket de venta al cliente.
	 */
	protected void entregarTicket(){
		this.getClienteQueDeseaRealizarLaCompra().recibirTicket(this.getTicketDeVenta());
	}
	
	/**
	 * @return que no es una venta con envio
	 */
	public boolean soyUnaVentaConEnvio(){
		return false;
	}
	
	/**
	 * si la venta se realizo con exito, osea si se compraron
	 * productos o no.
	 * @return
	 */
	public boolean seRealizoConExito() {
		return this.numeroDeProductosComprados() > 0;
	}
	
	
	/**
	 * @return la venta transformada en una venta.
	 * @throws VentasException 
	 */
	public VentaConEnvioYaPagado enviarVentaRealizada() throws VentasException{
		VentaConEnvioYaPagado venta;
		if(!this.seRealizoConExito()){
			throw new VentasException("No se puede Enviar una venta"
					+ " no abonada.");
		}
		else{
			venta = new VentaConEnvioYaPagado(
							this.getListaDeCompraDelCliente(),
							this.getSucursalDondeSeRealizaLaVenta(),
							this.getClienteQueDeseaRealizarLaCompra()) ;
		}
		return venta; 
	}
	
	/**
	 * genera un  tiket por la compra.
	 */
	protected void generarTicketDeCompra(){
		this.setTicketDeVenta(new Ticket(
				this.montoAPagarPorLosProductos(), DateTime.now(),
				this.getListaDeCompraDelCliente()));
	}

	/**
	 * @return que no es una venta directa sin cliente.
	 */
	public boolean soyUnaVentaDirectaSinCliente(){
		return false;
	}
	
	/**
	 * @return los productos que desea comprar.
	 */
	public Map<Producto, Integer> getProductosQueDeseaComprar() {
		return productosQueDeseaComprar;
	}

	/**
	 * @param productosQueDeseaComprar es
	 * los productos que desea comprar.
	 */
	protected void setProductosQueDeseaComprar(Map<Producto, Integer> productosQueDeseaComprar) {
		this.productosQueDeseaComprar = productosQueDeseaComprar;
	}

	/**
	 * @return los productos comprados.
	 */
	public Map<Producto, Integer> getProductosComprados() {
		return productosComprados;
	}

	/**
	 * @param productosComprados define los productos comprados.
	 */
	protected void setProductosComprados(Map<Producto, Integer> productosComprados) {
		this.productosComprados = productosComprados;
	}

	/**
	 * @return la sucursal donde se realiza la venta.
	 */
	public Sucursal getSucursalDondeSeRealizaLaVenta() {
		return sucursalDondeSeRealizaLaVenta;
	}

	/**
	 * @param sucursalDondeSeRealizaLaVenta es
	 * la sucursal donde se realiza la venta.
	 */
	protected void setSucursalDondeSeRealizaLaVenta(
			Sucursal sucursalDondeSeRealizaLaVenta) {
		this.sucursalDondeSeRealizaLaVenta = sucursalDondeSeRealizaLaVenta;
	}

	/**
	 * @return el cliente que desea realizar la compra.
	 */
	public Cliente getClienteQueDeseaRealizarLaCompra() {
		return clienteQueDeseaRealizarLaCompra;
	}

	/**
	 * @param clienteQueDeseaRealizarLaCompra es
	 * el cliente que desea realizar la compra.
	 */
	protected void setClienteQueDeseaRealizarLaCompra(
			Cliente clienteQueDeseaRealizarLaCompra) {
		this.clienteQueDeseaRealizarLaCompra = clienteQueDeseaRealizarLaCompra;
	}

	/**
	 * @return la fecha de creacion de la Venta.
	 */
	public DateTime getFechaDeCreacionDeLaVenta() {
		return fechaDeCreacionDeLaVenta;
	}

	/**
	 * @param fechaDeCreacionDeLaVenta es
	 * la fecha de creacion de la Venta.
	 */
	protected void setFechaDeCreacionDeLaVenta(DateTime fechaDeCreacionDeLaVenta) {
		this.fechaDeCreacionDeLaVenta = fechaDeCreacionDeLaVenta;
	}
	
	/**
	 * @return el ticket de facturacion de la compra.
	 */
	public Ticket getTicketDeVenta() {
		return ticketDeVenta;
	}

	/**
	 * @param ticketDeVenta  es el ticket de facturacion de la compra.
	 */
	protected void setTicketDeVenta(Ticket ticketDeVenta) {
		this.ticketDeVenta = ticketDeVenta;
	}

	/**
	 * @return the listaDeCompraDelCliente
	 */
	public ListaDeCompras getListaDeCompraDelCliente() {
		return listaDeCompraDelCliente;
	}

	/**
	 * @param listaDeCompraDelCliente the listaDeCompraDelCliente to set
	 */
	public void setListaDeCompraDelCliente(ListaDeCompras listaDeCompraDelCliente) {
		this.listaDeCompraDelCliente = listaDeCompraDelCliente;
	}

}
